﻿using System;
using System.IO;
using Gmantis.Zip.ZLib;

namespace Gmantis.Zip
{
    public enum CompressionLevelEnum
    {
        BestCompression = 9,
        BestSpeed = 1,
        DefaultCompression = -1,
        NoCompression = 0
    }

    public class ZStreamWriter : Stream
    {
        // Fields
        private Stream _baseStream;
        private byte[] _buf;
        private bool _finished;
        private long _flushed;
        private bool _ownsBase;
        private byte[] _wb;
        private ZStream _z;

        // Methods
        internal ZStreamWriter()
        {
            this._wb = new byte[1];
        }

        public ZStreamWriter(Stream baseStream)
            : this(baseStream, CompressionLevelEnum.DefaultCompression, true, false)
        {
        }

        public ZStreamWriter(Stream baseStream, CompressionLevelEnum level)
            : this(baseStream, level, true, false)
        {
        }

        public ZStreamWriter(Stream baseStream, bool zip)
            : this(baseStream, CompressionLevelEnum.DefaultCompression, !zip, zip)
        {
        }

        public ZStreamWriter(Stream baseStream, CompressionLevelEnum level, bool zip)
            : this(baseStream, level, !zip, zip)
        {
        }

        public ZStreamWriter(Stream baseStream, bool header, bool crc32)
            : this(baseStream, CompressionLevelEnum.DefaultCompression, header, crc32)
        {
        }

        public ZStreamWriter(Stream baseStream, CompressionLevelEnum level, bool header, bool crc32)
        {
            this._wb = new byte[1];
            this.InitStream(baseStream, level, header, crc32);
        }

        public override void Close()
        {
            this.finish();
            if (this._ownsBase)
            {
                this._baseStream.Close();
            }
        }

        internal void finish()
        {
            if (!this._finished)
            {
                int num2;
                while (true)
                {
                    int num = this._z.deflate(4);
                    if (num == 1)
                    {
                        break;
                    }
                    if (num != 0)
                    {
                        throw new ZStreamException(StringTables.GetString("Error deflating: ") + this._z.msg);
                    }
                    num2 = this._buf.Length - this._z.avail_out;
                    if (num2 > 0)
                    {
                        this._baseStream.Write(this._buf, 0, num2);
                        this._z.avail_out = this._buf.Length;
                        this._z.next_out = this._buf;
                        this._z.next_out_index = 0;
                    }
                }
                if (this._z.deflateEnd() != 0)
                {
                    throw new ZStreamException(StringTables.GetString("Error deflating: ") + this._z.msg);
                }
                num2 = this._buf.Length - this._z.avail_out;
                if (num2 > 0)
                {
                    this._baseStream.Write(this._buf, 0, num2);
                }
                this._finished = true;
            }
        }

        internal void flush()
        {
            if (this._finished)
            {
                return;
            }
            while (true)
            {
                int num = this._z.deflate(2);
                if (num == 1)
                {
                    return;
                }
                if (num != 0)
                {
                    throw new ZStreamException(StringTables.GetString("Error deflating: ") + this._z.msg);
                }
                int count = this._buf.Length - this._z.avail_out;
                if (count > 0)
                {
                    this._baseStream.Write(this._buf, 0, count);
                    this._z.avail_out = this._buf.Length;
                    this._z.next_out = this._buf;
                    this._z.next_out_index = 0;
                }
            }
        }

        public override void Flush()
        {
            this.flush();
            this._baseStream.Flush();
        }

        internal void InitStream(Stream baseStream, CompressionLevelEnum level, bool header, bool crc32)
        {
            if ((baseStream == null) || !baseStream.CanWrite)
            {
                throw new ArgumentException(StringTables.GetString("baseStream must be writable stream."));
            }
            this._buf = new byte[0x4000];
            this._baseStream = baseStream;
            this._ownsBase = true;
            this._z = new ZStream(crc32);
            this._z.next_out = this._buf;
            this._z.avail_out = this._buf.Length;
            this._z.next_out_index = 0;
            if ((header ? this._z.deflateInit((int)level) : this._z.deflateInit((int)level, -15)) != 0)
            {
                throw new ZStreamException(StringTables.GetString("Failed to initialize compressed stream."));
            }
        }

        public override int Read(byte[] buf, int offset, int count)
        {
            throw new NotSupportedException(StringTables.GetString("Read not supported."));
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotSupportedException(StringTables.GetString("Seek not supported."));
        }

        public override void SetLength(long value)
        {
            throw new NotSupportedException(StringTables.GetString("SetLength not supported."));
        }

        internal void write(byte[] b, int off, int len)
        {
            if (len != 0)
            {
                if (this._finished)
                {
                    throw new ZStreamException(StringTables.GetString("Can't write data after call to Finish()."));
                }
                this._z.next_in = b;
                this._z.next_in_index = off;
                this._z.avail_in = len;
                do
                {
                    int flush = 0;
                    if ((this._z.total_in - this._flushed) > 0x7d000L)
                    {
                        flush = 2;
                        this._flushed = this._z.total_in;
                    }
                    if (this._z.deflate(flush) != 0)
                    {
                        throw new ZStreamException(StringTables.GetString("Error deflating: ") + this._z.msg);
                    }
                    int count = this._buf.Length - this._z.avail_out;
                    if (count > 0)
                    {
                        this._baseStream.Write(this._buf, 0, count);
                        this._z.avail_out = this._buf.Length;
                        this._z.next_out = this._buf;
                        this._z.next_out_index = 0;
                    }
                }
                while (this._z.avail_in > 0);
            }
        }

        public override void Write(byte[] buf, int offset, int count)
        {
            int num = 500;
            for (int i = offset; i < (offset + count); i += num)
            {
                int len = Math.Min(num, (offset + count) - i);
                this.write(buf, i, len);
            }
        }

        public override void WriteByte(byte value)
        {
            this._wb[0] = value;
            this.write(this._wb, 0, 1);
        }

        // Properties
        public Stream BaseStream
        {
            get
            {
                return this._baseStream;
            }
        }

        public override bool CanRead
        {
            get
            {
                return false;
            }
        }

        public override bool CanSeek
        {
            get
            {
                return false;
            }
        }

        public override bool CanWrite
        {
            get
            {
                return true;
            }
        }

        public int Checksum
        {
            get
            {
                return (int)this._z.adler;
            }
        }

        public override long Length
        {
            get
            {
                return this._baseStream.Length;
            }
        }

        public bool OwnsBaseStream
        {
            get
            {
                return this._ownsBase;
            }
            set
            {
                this._ownsBase = value;
            }
        }

        public override long Position
        {
            get
            {
                return this._baseStream.Position;
            }
            set
            {
                throw new NotSupportedException(StringTables.GetString("Seek not supported."));
            }
        }

        public int SizeCompressed
        {
            get
            {
                if (this._z.total_out > 0x7fffffffL)
                {
                    throw new ZipFileException(StringTables.GetString("Size is too large to be represented as an Int32, use SizeCompressedLong instead."));
                }
                return (int)this._z.total_out;
            }
        }

        public long SizeCompressedLong
        {
            get
            {
                return this._z.total_out;
            }
        }

        public int SizeUncompressed
        {
            get
            {
                if (this._z.total_in > 0x7fffffffL)
                {
                    throw new ZipFileException(StringTables.GetString("Size is too large to be represented as an Int32, use SizeUncompressedLong instead."));
                }
                return (int)this._z.total_in;
            }
        }

        public long SizeUncompressedLong
        {
            get
            {
                return this._z.total_in;
            }
        }

        public ZStream ZStream
        {
            get
            {
                return this._z;
            }
        }
    }
}
